import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.math.BigInteger;
import java.net.URL;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.Scanner;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Vector;
import java.math.BigInteger;



public class CellularAutomata {

	static Integer highestHighestFit = 0;
	static RuleSet bestRule=null;
	static Float mutationRate;
	static Integer ruleSize;
	static Integer numberOfGAGenerations;
	static Integer tournamentSize;
	static int radius = 2;
	static Set<String> tested = new HashSet<String>();
	Properties properties;
	private static final int GOL_GENERATIONS=200;
	public static void main (String args[]) 
	{
		String fileName;
		if(args.length == 1)
		{
			fileName = args[0];
		}
		else
		{
			System.out.println("Missing file name");
			return;
		}
		
		
		int xSize = 50;
		int ySize = 50;
		DisplayGrid seedGrid = new DisplayGrid(ySize,xSize,"Seed");
		SeedPopulation seed = new SeedPopulation(xSize,ySize, seedGrid,true);
		DisplayGrid goalGrid = new DisplayGrid(ySize,xSize,"Goal");
		GoalPopulation goal  = new GoalPopulation(ySize,xSize,goalGrid);
		//DisplayGrid grid = new DisplayGrid(ySize,xSize,"Evolved");
		EvolvedPopulation evolved= new EvolvedPopulation(seed,null);
		File file = new File(fileName);
		Scanner scanner;
		try{
			scanner = new Scanner(file);
		}catch(FileNotFoundException e)
		{
			System.out.println("File not found " + e.getMessage());
			return;
		}
		scanner.nextLine();
		File outputFile = new File("output" + getDateForFileName());
		try{
			outputFile.createNewFile();
		}catch(Exception e)
		{
			System.out.println(e.getMessage());
			return;
		}
		PrintStream writer = null;
		try{
			writer = new PrintStream(new FileOutputStream(outputFile));
		}catch(Exception e)
		{
			System.out.println(e.getMessage());
		}
		while(scanner.hasNext())
		{
			highestHighestFit = 0;
			bestRule = null;
			mutationRate = scanner.nextFloat();
			System.out.println("Got mutationRate: " + mutationRate);
			ruleSize = scanner.nextInt();
			System.out.println("Got ruleSize of: " + ruleSize);
			numberOfGAGenerations = scanner.nextInt();
			System.out.println("Got: " + numberOfGAGenerations);
			tournamentSize = scanner.nextInt();
			System.out.println("Got: " + tournamentSize);

			List<Pair<Integer,RuleSet>> results = doFirstGeneration(ruleSize,evolved,seed,goal);
			System.out.println("First generation done");
			Set<RuleSet> currentResults;
			for(int i=0; i<numberOfGAGenerations; i++)
			{
				currentResults = runGA(results);			
				results = doGeneration(currentResults,evolved,seed,goal);
				System.out.println("Completed GA number: " + i);
			}
			System.out.println("Final best of the best fit: " + highestHighestFit);
//			DisplayGrid finalGrid = new DisplayGrid(ySize,xSize,"Final");
//			EvolvedPopulation finalEvolved = new EvolvedPopulation(seed,finalGrid);
//			GameOfLife gol = new GameOfLife();
//			gol.reset(finalEvolved, bestRule);
//			gol.runSimulation(GOL_GENERATIONS);

			writer.print(highestHighestFit.toString() + " ");

			try{
				bestRule.outputToFile(writer);
				writer.println();
			}catch(Exception e)
			{
				System.out.println("Error writing file : " + e.getMessage());
				return;
			}
			evolved.reset(seed);
		}
		try{
			writer.flush();
			writer.close();
		}catch(Exception e)
		{
			System.out.println(e.getMessage());
			return;
		}
	}
	
	public static Set<RuleSet> runGA(List<Pair<Integer,RuleSet>> results)
	{
		Set<RuleSet> highFit = binaryTournament(results,tournamentSize);
		assert(highFit.size() == results.size());
		return highFit;
	}
	
	
	
	public static Set<RuleSet> binaryTournament(List<Pair<Integer,RuleSet>> results,int tournamentSize)
	{
		
		Set<RuleSet> highFit = new HashSet<RuleSet>();
		//Select a new set of results of the same size from the current set
		//by selecting tournamentSize rules at random and performing a Binary Tournament selection
		//until you reach a new set with the highest Fit rules
		Random rand = new Random(Math.abs(System.nanoTime()));
		int size = results.size();
		while(highFit.size() < size)
		{
			Integer bestSoFar = 0;
			Integer indexOfBestSoFar = 0;
			Integer secondBest = 0;
			Integer indexOfSecondBest = 0;
			for(int j=0;j<tournamentSize; j++)
			{	
				Integer indexToCheck = rand.nextInt(size);
				Integer current= results.get(indexToCheck).first;
				if(current > bestSoFar)
				{
					secondBest = bestSoFar;
					indexOfSecondBest=indexOfBestSoFar;
					bestSoFar = current;
					indexOfBestSoFar = indexToCheck;
				}
				else if(current > secondBest)
				{
					secondBest = current;
					indexOfSecondBest = indexToCheck;
				}
			}
			if(bestSoFar > highestHighestFit)
			{
				highestHighestFit = bestSoFar;
				System.out.println("Better value found! " + highestHighestFit);
				bestRule = results.get(indexOfBestSoFar).second.copy();
			}
			RuleSet newRule1 = results.get(indexOfBestSoFar).second.copy();
			RuleSet newRule2 = results.get(indexOfSecondBest).second.copy();
			newRule1.crossover(newRule2);
			newRule1.mutate(mutationRate);
			newRule2.mutate(mutationRate);
			String rule1 = newRule1.convertToString();
			String rule2 = newRule2.convertToString();
			if(!(tested.contains(rule1)) && !(tested.contains(rule2)))
			{
				tested.add(rule1);
				tested.add(rule2);
				highFit.add(newRule1);
				highFit.add(newRule2);
			}
			else
			{
				System.out.println("Found a match");
			}
		}
		return highFit;
	}
	
	public static List<Pair<Integer,RuleSet>> doFirstGeneration(int numberOfRules,EvolvedPopulation evolved, SeedPopulation seed, 
			GoalPopulation goal)
	{
		RuleSet rules = new RuleSet(radius);
		
		GameOfLife gol = new GameOfLife(evolved, rules, radius);

		List<Pair<Integer,RuleSet>> comparedRules = new Vector<Pair<Integer,RuleSet>>();
		int i=0;
		while(i<numberOfRules)
		{	
			System.out.println("Calculating rule: " + i);
			long start = System.currentTimeMillis();	
			gol.runSimulation(GOL_GENERATIONS);
			System.out.println("One run took: " +(System.currentTimeMillis() - start));			
			comparedRules.add(new Pair<Integer,RuleSet>(goal.compare(evolved), rules));
			i++;
			rules = new RuleSet(radius);
			evolved.reset(seed);
			gol.reset(evolved,rules, radius);
			System.out.println("Completed calculating rule: " + i);
		}
		return comparedRules;
	}
	
	public static List<Pair<Integer,RuleSet>> doGeneration(Set<RuleSet> rulesSet,
			EvolvedPopulation evolved, SeedPopulation seed, GoalPopulation goal)
	{
		Iterator<RuleSet> iter = rulesSet.iterator();
		GameOfLife gol = new GameOfLife();
		List<Pair<Integer,RuleSet>> comparedRules = new Vector<Pair<Integer,RuleSet>>();
		evolved.reset(seed);
		long start = 0;
		while(iter.hasNext())
		{
			start = System.currentTimeMillis();
			RuleSet rules = iter.next();
			gol.reset(evolved, rules, radius);
			
			gol.runSimulation(GOL_GENERATIONS);
			comparedRules.add(new Pair<Integer,RuleSet>(goal.compare(evolved), rules));
			evolved.reset(seed);
			gol.reset(evolved,rules, radius);
			//System.out.println("One run took: " +(System.currentTimeMillis() - start));	
		}
		return comparedRules;
	}
	
	public static String getDateForFileName()
	{
		String ret = new String();
		Calendar cal = Calendar.getInstance();
		
		ret += cal.get(Calendar.DAY_OF_MONTH);
		ret += "_";
		ret += cal.get(Calendar.MONTH);
		ret +="_";
		ret += cal.get(Calendar.HOUR_OF_DAY);
		ret += "_";
		ret += cal.get(Calendar.MINUTE);
		ret += "_";
		ret += cal.get(Calendar.SECOND);
		ret += ".txt";
		return ret;
	}
	
	public void loadProperties(String fileName)
	{
		this.properties = new Properties() ;
		URL url =  ClassLoader.getSystemResource(fileName);
		try{
			properties.load(new FileInputStream(new File(url.getFile())));
		}catch(IOException e)
		{
			System.out.println("Could not load properties file");
		}
	}
}
